use crate::context::Context;
use crate::defines::OtaVersion;
use embed_std::sync::arc::Arc;
use embed_std::thread::sleep_ms;
use embed_std::{Error, Result};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
use std::time::Duration;
pub const API_QUEUE_SIZE: u32 = 100;

#[derive(Debug)]
pub(crate) struct Request {
    pub req: RequestParam,
    //ms
    pub timeout: u32,
    pub resp: Option<SyncSender<Result<Response>>>,
}

#[derive(Debug)]
pub(crate) enum RequestParam {
    Quit,
    OtaVersion,
    IsConnected,
}

pub(crate) enum Response {
    None,
    OtaVersion(OtaVersion),
    IsConnected(bool),
}

pub struct Api {
    tx: SyncSender<Request>,
}

impl Api {
    pub fn get_ota_version(&self, to: u32) -> Result<OtaVersion> {
        match self.call(
            RequestParam::OtaVersion,
            if to == 0 { None } else { Some(to) },
        )? {
            Response::OtaVersion(v) => Ok(v),
            _ => Err(Error::Other("invalid response".to_owned())),
        }
    }
    pub fn quit(&self) -> Result<()> {
        _ = self.call(RequestParam::Quit, None)?;
        Ok(())
    }
    pub fn is_connected(&self) -> bool {
        match self.call(RequestParam::IsConnected, Some(1_000)) {
            Ok(Response::IsConnected(b)) => return b,
            _ => return false,
        }
    }

    fn call(&self, req: RequestParam, timeout: Option<u32>) -> Result<Response> {
        let mut to = 0;
        let (r, rx) = if let Some(t) = timeout {
            to = t;
            let (tx, rx) = sync_channel(2);
            (
                Request {
                    req,
                    timeout: t,
                    resp: Some(tx),
                },
                Some(rx),
            )
        } else {
            (
                Request {
                    req,
                    timeout: 0,
                    resp: None,
                },
                None,
            )
        };
        self.tx.send(r).map_err(|e| Error::Other(e.to_string()))?;
        if let Some(rx) = rx {
            const STEP: u32 = 10;
            for _ in 0..to / STEP {
                if let Ok(v) = rx.try_recv() {
                    return Ok(v?);
                }
                sleep_ms(STEP);
            }
            return Err(Error::Timeout);
        }
        Ok(Response::None)
    }
    pub fn handle_api(
        &self,
        ctx: &Context,
        req: &Request,
        is_quit: &Arc<AtomicBool>,
    ) -> Result<Response> {
        if !ctx.is_connected() {
            return Err(Error::Other("disconnected".to_string()));
        }
        match req.req {
            RequestParam::Quit => {
                is_quit.store(true, Ordering::SeqCst);
                Ok(Response::None)
            }
            RequestParam::OtaVersion => {
                let v = ctx.get_ota_version()?;
                Ok(Response::OtaVersion(v))
            }
            RequestParam::IsConnected => Ok(Response::IsConnected(ctx.is_connected())),
        }
    }
}

static mut API: Option<Api> = None;
pub(crate) fn set_api(tx: SyncSender<Request>) {
    unsafe { API = Some(Api { tx }) }
}

pub fn api() -> Option<&'static Api> {
    unsafe { API.as_ref() }
}
